类加载分为7个步骤,分别是:加载,验证,准备,解析,初始化,使用,卸载。加载阶段是jvm将class字节码文件将类装载到内存当中,执行这一操作的是类加载器。
系统由三种类加载器
BootStrap ClassLoader, 启动类加载器,负责加载Java的核心类库,如/lib/rt.jar,lib/resources.jar等,它由C++编写
Extension ClassLoader, 扩展类加载器,负责加载Java的扩展类库,默认加载JAVA_HOME/jre/lib/ext/目下的所有jar。
App ClassLoader, 系统类加载器,负责加载应用程序classpath目录下的所有jar和class文件。
自定义类加载器,一般继承3,实现自定义的类加载器
一般的,Java采用双亲委派机制进行类加载,即首先委派父类加载器进行加载,如果父类能够加载,则自己不用加载,否则尝试自己加载。这样的好处就是,对于同一个类,如核心类,都是由同一个类加载器进行加载。在判断两个类是否相同时,除了比较它们的全限定名,还是比较他们的类加载器,不同的类加载器加载同一份字节码,在系统中也会被视为不同的类。
而,本文,要讨论的就是破坏双亲委派机制。试想如果在核心类中,想要加载一个用户自己编写的类,那么,由于核心类是由启动类加载器加载的,根据双亲委派机制,它无法委派自己的孩子-系统类加载器进行加载,那该如何解决呢?
事实上,这种情况经常发生。
JNDI服务:JNDI的目的就是对资源进行集中管理和查找,它需要调用独立厂商实现部部署在应用程序的classpath下的JNDI接口提供者(SPI, Service Provider Interface)的代码。
因此,为了破坏这种双亲委派机制,在启动类里也能够使用系统类加载器对类进行加载,Java设计了线程上下文加载器。在核心类进行类加载时,可以读取当前线程的线程上下文加载器,使用该加载器加载实现了SPI接口的类。
Java中所有涉及SPI的加载动作基本上都采用这种方式,例如JNDI,JDBC,JCE,JAXB和JBI等
JDBC
JDBC是一个典型的破坏了双亲委派机制的案例
|
|
DriverManager
是Java的核心类,
|
|
在getConnection
函数里,得到了调用者对象的类,这里是JdbcUtils,因此可以在rt类的代码里使用JdbcUtils的ClassLoader进行类加载。但是源码其实并没这么做,注意到我们自己写的类中有Class.forName("com.mysql.jdbc.Driver").newInstance();
这句话了吗?这里已经加载了一个Driver,MYSQL的Driver,而在DriverManager中,会遍历所有Driver,使用Class clazz = Class.forName(Driver.getClass().getName,true,classLoader)
,使用传进来的classLoader对全有驱动的 全限定名进行重新加载,在比较clazz和Driver.getClass()是否相等。
tomcat
WebappClassLoader内部重写了loadClass和findClass方法,实现了绕过“双亲委派”直接加载web应用内部的资源,当然可以通过在Context.xml文件中加上